#include "matrix.h"
#include "device.h"
#include "uart.h"
#include "GICv3.h"
#include "interrupt.h"
#include "bit.h"
#include "console.h"
#include "semaphore.h"
#include "ringbuffer.h"
#include "error.h"

#define UART0_BUF_SIZE 1024

typedef enum
{
    UART_BAUD_9600,
    UART_BAUD_115200,
} uart_baud_t;

typedef struct
{
    char name[MX_DEVICE_NAME_LEN];
    char drvname[MX_DEVICE_NAME_LEN];
    uart_baud_t baudrate;
    mx_usize_t rtimeout;        // read timeout (tick)
    mx_uint8_t irqnum;
    mx_semaphore_t sem;
    volatile mx_bool_t wflag;   // wait flag
    mx_ringbuffer_t rbuf;       // receive ringbuffer
    struct
    {
        mx_uint16_pt dr;
        mx_uint16_pt fr;
        mx_uint16_pt cr;
        mx_uint16_pt imsc;
        mx_uint16_pt ris;
        mx_uint16_pt icr;
    } uart;
    mx_uint8_t buf[UART0_BUF_SIZE];
} uart_config_t;

uart_config_t uart0config = {
    .name = "uart0",
    .drvname = "",
    .baudrate = UART_BAUD_115200,
    .rtimeout = 0,
    .irqnum = PL011_UART0_IRQNUM,
    .wflag = mx_false,
    .uart = {
        .dr = (mx_uint16_pt)PL011_UARTDR,
        .fr = (mx_uint16_pt)PL011_UARTFR,
        .cr = (mx_uint16_pt)PL011_UARTCR,
        .imsc = (mx_uint16_pt)PL011_UARTIMSC,
        .ris = (mx_uint16_pt)PL011_UARTRIS,
        .icr = (mx_uint16_pt)PL011_UARTICR
    }
};

static mx_device_t dev_uart0;

static mx_err_t uart0_open (mx_device_pt pdev)
{
    uart_config_t* ucfg = (uart_config_t*)pdev->data;
    /* enable gic interrupt */
    EnableSPI(ucfg->irqnum);
    /* enable uart interrupt */
    SETBIT(*ucfg->uart.imsc, 4);
    /* enable uart */
    SETBIT(*ucfg->uart.cr, 0);

    return EOK;
}

static void uart0_close (mx_device_pt pdev)
{
    uart_config_t* ucfg = (uart_config_t*)pdev->data;
    /* disable uart interrupt */
    CLSBIT(*ucfg->uart.imsc, 4);
    /* disable gic interrupt */
    DisableSPI(ucfg->irqnum);
    /* disable uart */
    CLSBIT(*ucfg->uart.cr, 0);
}

static mx_size_t uart0_read (mx_device_pt pdev, mx_void_pt buf, mx_size_t size)
{
    mx_size_t ret = 0;
    mx_usize_t cnt;
    uart_config_t* ucfg = (uart_config_t*)pdev->data;
    cnt = mx_ringbuffer_count(&ucfg->rbuf);
    if (cnt == 0)
    {
        ucfg->wflag = mx_true;
        if (ucfg->rtimeout == 0)
        {
            mx_semaphore_waitforever(&ucfg->sem);
        }
        else
        {
            mx_semaphore_wait(&ucfg->sem, ucfg->rtimeout);
        }
        cnt = mx_ringbuffer_count(&ucfg->rbuf);
    }
    if (cnt > 0)
    {
        ret = size > cnt ? cnt : size;
        for (mx_size_t i = 0; i < ret; i++)
        {
            ((mx_uint8_pt)buf)[i] = (mx_uint8_t)mx_ringbuffer_getchar(&ucfg->rbuf);
        }
    }
    return ret;
}

static mx_size_t uart0_write (mx_device_pt pdev, mx_void_pt buf, mx_size_t size)
{
    uart_config_t* ucfg = (uart_config_t*)pdev->data;
    while (*(ucfg->uart.fr) & (1 << PL011_UARTFR_TXFF_BIT));
    for (mx_size_t i = 0; i < size; i++)
    {
        *(ucfg->uart.dr) = ((mx_uint8_pt)buf)[i];
    }
    return size;
}

static mx_err_t uart0_ctrl (mx_device_pt pdev, mx_size_t ctrl, mx_void_pt arg)
{
    mx_err_t ret = EERR;
    uart_config_t* ucfg = (uart_config_t*)pdev->data;
    switch (ctrl)
    {
    case 1: // read timeout configuration
        ucfg->rtimeout = (mx_usize_t)arg;
        ret = EOK;
        break;
    
    default:
        break;
    }
    return ret;
}

static mx_device_ops_t ops = {
    .open = uart0_open,
    .close = uart0_close,
    .read = uart0_read,
    .write = uart0_write,
    .ctrl = uart0_ctrl
};

/* uart interrupt handler */
static void uart0_intr_handler (mx_void_pt param)
{
    uart_config_t* ucfg = (uart_config_t*)param;
    if (!(*(ucfg->uart.fr) & UARTFR_RXFE))
    {
        mx_ringbuffer_putchar(&ucfg->rbuf, (mx_uint8_t)*(ucfg->uart.dr));
        if (ucfg->wflag)
        {
            ucfg->wflag = mx_false;
            mx_semaphore_sign(&ucfg->sem);
        }
    }
}

void device_uart_init ()
{
    /* initialize semaphore */
    mx_semaphore_init(&uart0config.sem, 1, 0);
    /* initialize ringbuffer */
    mx_ringbuffer_init(&uart0config.rbuf, uart0config.buf, UART0_BUF_SIZE);
    
    /* uart spi interrupt configuration */
    SetSPIRoute(uart0config.irqnum, 0, gicdirouter_ModeSpecific);   // Route INTID 33 to 0.0.0.0 (this core)
    SetSPIPriority(uart0config.irqnum, 0);                          // Set INTID 33 to priority to 0
    ConfigureSPI(uart0config.irqnum, gicdicfgr_Level);              // Set INTID 33 as level-sensitive

    /* register uart interrupt handler to system */
    mx_interrupt_register("uart0-int33", PL011_UART0_IRQNUM, uart0_intr_handler, &uart0config);

    mx_device_mount(&dev_uart0, (mx_device_data_pt)&uart0config, &ops);
}
MX_BOARDINIT_EXPORT(device_uart_init, 1);
